package io.gitee.zhangbinhub.admin.controller.api

import cn.dev33.satoken.annotation.SaCheckRole
import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts
import cn.dev33.satoken.stp.StpUtil
import io.gitee.zhangbinhub.acp.boot.exceptions.WebException
import io.gitee.zhangbinhub.acp.boot.http.HttpHeaders
import io.gitee.zhangbinhub.acp.boot.http.HttpStatus
import io.gitee.zhangbinhub.acp.boot.log.LogAdapter
import io.gitee.zhangbinhub.acp.boot.vo.ErrorVo
import io.gitee.zhangbinhub.acp.core.common.CommonTools
import io.gitee.zhangbinhub.admin.api.ServerApi
import io.gitee.zhangbinhub.admin.base.BaseController
import io.gitee.zhangbinhub.admin.constant.OauthConstant
import io.gitee.zhangbinhub.admin.constant.RoleCode
import io.gitee.zhangbinhub.admin.service.ApplicationService
import io.gitee.zhangbinhub.admin.service.UserService
import io.gitee.zhangbinhub.admin.tools.TokenTools
import io.gitee.zhangbinhub.admin.vo.InfoVo
import io.gitee.zhangbinhub.admin.vo.OnlineInfoVo
import io.swagger.annotations.Api
import io.swagger.annotations.ApiOperation
import io.swagger.annotations.ApiParam
import org.noear.solon.Solon
import org.noear.solon.annotation.Body
import org.noear.solon.annotation.Controller
import org.noear.solon.annotation.Mapping
import org.noear.solon.annotation.Path
import org.noear.solon.core.handle.MethodType
import org.noear.solon.core.util.MimeType
import org.noear.solon.net.http.HttpUtils
import org.noear.solon.validation.annotation.NotEmpty
import org.noear.solon.validation.annotation.NotNull
import org.noear.solon.validation.annotation.Valid

@Valid
@Controller
@Api("登录信息")
open class TokenController(
    private val logAdapter: LogAdapter,
    private val applicationService: ApplicationService,
    private val userService: UserService,
) : BaseController(logAdapter) {
    @Throws(WebException::class)
    private fun innerToken(paramMap: MutableMap<String, Any>): String {
        val clientId = paramMap[OauthConstant.clientIdKey]?.toString()
        if (CommonTools.isNullStr(clientId)) {
            throw WebException("${OauthConstant.clientIdKey} 不能为空")
        }
        val clientSecret = paramMap[OauthConstant.clientSecretKey]?.toString()
        if (CommonTools.isNullStr(clientSecret)) {
            throw WebException("${OauthConstant.clientSecretKey} 不能为空")
        }
        return try {
            HttpUtils.http("http://localhost:${Solon.cfg().serverPort()}${SaOAuth2Consts.Api.token}")
                .timeout(60)
                .header(HttpHeaders.CONTENT_TYPE, MimeType.APPLICATION_FORM_URLENCODED_VALUE)
                .data(paramMap)
                .exec("POST").also { response ->
                    if (response.code() != HttpStatus.OK.value()) {
                        val errorVo = response.bodyAsBean<ErrorVo>(ErrorVo::class.java)
                        throw WebException(HttpStatus.UNAUTHORIZED, errorVo.errorDescription)
                    }
                }.bodyAsString()
        } catch (e: Exception) {
            throw WebException(HttpStatus.UNAUTHORIZED, e.message)
        }
    }

    @ApiOperation("申请token")
    @Mapping(
        method = [MethodType.POST],
        value = ServerApi.basePath + ServerApi.token,
        produces = MimeType.APPLICATION_JSON_VALUE
    )
    @Throws(WebException::class)
    fun doLogin(@Body paramMap: MutableMap<String, Any>): String = innerToken(paramMap)

    @ApiOperation("注销当前用户")
    @Mapping(
        method = [MethodType.POST],
        value = ServerApi.basePath + ServerApi.logOut,
        produces = MimeType.APPLICATION_JSON_VALUE
    )
    @Throws(WebException::class)
    fun doLogOut(): InfoVo =
        try {
            logAdapter.info("用户[loginNo=" + StpUtil.getLoginIdAsString() + "]主动下线!")
            TokenTools.revokeAccessToken()
            InfoVo(message = "成功下线")
        } catch (e: Exception) {
            throw WebException(e.message)
        }

    @ApiOperation("获取各应用在线用户数统计")
    @Mapping(
        method = [MethodType.GET],
        value = ServerApi.basePath + ServerApi.onlineInfo,
        produces = MimeType.APPLICATION_JSON_VALUE
    )
    @Throws(WebException::class)
    fun getOnlineInfo(): List<OnlineInfoVo> = try {
        mutableListOf<OnlineInfoVo>().apply {
            val infoList = StpUtil.searchTokenValue("", 0, -1, true).map { tokenValue ->
                StpUtil.getTerminalInfoByToken(tokenValue.replace(StpUtil.stpLogic.splicingKeyTokenValue(""), ""))
            }
            applicationService.getOwnAppList(StpUtil.getLoginIdAsString()).forEach {
                this.add(
                    OnlineInfoVo(
                        appId = it.id,
                        appName = it.appName,
                        count = infoList.filter { info -> info?.deviceType == it.id }.size.toLong()
                    )
                )
            }
        }
    } catch (e: Exception) {
        throw WebException(e.message)
    }

    @ApiOperation("指定应用下的用户强制下线")
    @SaCheckRole(RoleCode.SUPER)
    @Mapping(
        method = [MethodType.DELETE],
        value = ServerApi.basePath + ServerApi.onlineInfo + "/{appId}",
        produces = MimeType.APPLICATION_JSON_VALUE
    )
    @Throws(WebException::class)
    fun delete(
        @ApiParam("应用id", required = true)
        @Path(name = "appId")
        appId: String,
        @ApiParam("用户id列表", required = true)
        @NotEmpty(message = "id不能为空")
        @NotNull(message = "id不能为空")
        @Body
        idList: List<String>
    ): InfoVo = try {
        idList.forEach {
            val userInfo = userService.getUserInfoById(it) ?: throw WebException("找不到该用户信息")
            TokenTools.revokeAccessToken(appId, userInfo.loginNo)
            logAdapter.info("用户[" + userInfo.name + "(" + userInfo.loginNo + ")]被管理员强制下线!")
        }
        InfoVo(message = "成功下线")
    } catch (e: Exception) {
        throw WebException(e.message)
    }

}
